package org.rcgwt.client.ui.grid.provider;

import org.rcgwt.client.ui.grid.IIndexList;
import org.rcgwt.client.ui.grid.event.BeforeCheckEvent;
import org.rcgwt.client.ui.grid.event.IIndexesState;
import org.rcgwt.client.ui.grid.internal.HandlerManagerProvider;
import org.rcgwt.client.ui.grid.internal.IndexesState;
import org.rcgwt.client.ui.grid.internal.IntRangeList;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.HandlerRegistration;

/**
 * Default {@link ISelectionProvider} implementation
 * 
 * @author Olivier Oeuillot
 * 
 */
public class BasicSelectionProvider extends HandlerManagerProvider implements
        ISelectionProvider {

    private final IntRangeList currentSelectedRows = new IntRangeList();

    private final Cardinality selectionCardinality;

    public BasicSelectionProvider(Cardinality cardinality) {
        this.selectionCardinality = cardinality;
    }

    public BasicSelectionProvider() {
        this(Cardinality.ZEROMANY);
    }

    @Override
    public void setSelection(int... rowIndexes) {
        setSelection(new IntRangeList(rowIndexes));
    }

    @Override
    public void setSelection(int start, int count) {
        setSelection(new IntRangeList(start, count));
    }

    @Override
    public void setSelection(IIndexList indexList) {
        assert indexList != null : "Index list is NULL";

        IntRangeList newSelected = ((IntRangeList) indexList).clone();

        IntRangeList deselected = IntRangeList.EMPTY_RANGE_LIST;
        if (currentSelectedRows.isEmpty() == false) {
            deselected = currentSelectedRows.clone();
            deselected.remove(newSelected);

            newSelected.remove(currentSelectedRows);
        }

        if (false) {
            Log.debug("SET   Selection=" + indexList);
            Log.debug("     DeSelected=" + deselected);
            Log.debug("    NewSelected=" + newSelected);
            Log.debug("        Current=" + currentSelectedRows
                    + " (event not performed yet)");
        }

        if (newSelected.isEmpty() && deselected.isEmpty()) {
            return;
        }

        // Remove the reselect
        processSelection(newSelected, deselected);
    }

    @Override
    public void appendSelection(int... rowIndexes) {
        appendSelection(new IntRangeList(rowIndexes));
    }

    @Override
    public void appendSelection(int start, int count) {
        appendSelection(new IntRangeList(start, count));
    }

    @Override
    public void appendSelection(IIndexList indexList) {
        assert indexList != null : "Index list is NULL";

        IntRangeList newSelected = ((IntRangeList) indexList).clone();
        newSelected.remove(currentSelectedRows);

        if (false) {
            Log.debug("APPEND   Selection=" + indexList);
            Log.debug("       NewSelected=" + newSelected);
            Log.debug("           Current=" + currentSelectedRows);
        }

        if (newSelected.isEmpty()) {
            return;
        }

        processSelection(newSelected, IntRangeList.EMPTY_RANGE_LIST);
    }

    @Override
    public void clearAllSelection() {

        Log.debug("CLEAR ALL Selection");
        Log.debug("   Current=" + currentSelectedRows);

        processSelection(IntRangeList.EMPTY_RANGE_LIST, currentSelectedRows);
    }

    @Override
    public void clearSelection(int... rowIndexes) {
        clearSelection(new IntRangeList(rowIndexes));
    }

    @Override
    public void clearSelection(int start, int count) {
        clearSelection(new IntRangeList(start, count));
    }

    @Override
    public void clearSelection(IIndexList indexList) {
        assert indexList != null : "Index list is NULL";

        IntRangeList deselected = currentSelectedRows
                .intersects((IntRangeList) indexList);

        Log.debug("CLEAR DeSelection=" + indexList);
        Log.debug("       DeSelected=" + deselected);
        Log.debug("          Current=" + currentSelectedRows);

        if (deselected.isEmpty()) {
            return;
        }

        processSelection(IntRangeList.EMPTY_RANGE_LIST, deselected);
    }

    @Override
    public int[] getSelection() {
        return currentSelectedRows.toArray();
    }

    @Override
    public IIndexList getSelectionList() {
        return currentSelectedRows.clone();
    }

    @Override
    public boolean isRowSelected(int rowIndex) {
        return currentSelectedRows.contains(rowIndex);
    }

    @Override
    public Cardinality getCardinality() {
        return selectionCardinality;
    }

    @Override
    public HandlerRegistration addSelectionHandler(
            SelectionHandler<IIndexesState> handler) {
        return ensureHandlers().addHandler(SelectionEvent.getType(), handler);
    }

    @Override
    public HandlerRegistration addBeforeSelectionHandler(
            BeforeSelectionHandler<IIndexesState> handler) {
        return ensureHandlers().addHandler(BeforeSelectionEvent.getType(),
                handler);

    }

    @Override
    public int getSelectionCount() {
        return currentSelectedRows.size();
    }

    /**
     * Internal call
     * 
     * @param newSelected
     * @param unselected
     * @param allUnselected
     * @return
     */
    protected boolean processSelection(IntRangeList newSelected,
            IntRangeList unselected) {

        assert newSelected != null : "NewSelected list is NULL";
        assert unselected != null : "Unselected list is NULL";

        IndexesState selectionState = new IndexesState(currentSelectedRows,
                newSelected, unselected);

        if (getHandlerCount(BeforeCheckEvent.getType()) > 0) {
            // perform before

            BeforeSelectionEvent<IIndexesState> event = BeforeSelectionEvent
                    .fire(this, selectionState);

            if (event.isCanceled()) {
                return false;
            }
        }

        if (unselected.isEmpty() == false) {
            currentSelectedRows.remove(unselected);
        }

        if (newSelected.isEmpty() == false) {
            currentSelectedRows.addInterval(newSelected);
        }

        if (false) {
            Log.debug("Change SELECTION to=" + currentSelectedRows);
        }

        SelectionEvent.fire(this, selectionState);

        return true;

    }

}
